package com.kqzz.common.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.util.Assert;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.ObjectSerializer;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.kqzz.common.config.log.mdc.LogInterceptor;
import com.kqzz.common.exception.CustomOauthException;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
	
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
		// 注册日志链路追踪拦截器
        registry.addInterceptor(new LogInterceptor())
        		.addPathPatterns("/**")
        		.excludePathPatterns("/login","/captcha","/download","/upload","/oauth/**","/error",
        				"/webjars/**","/actuator/**","/webjars/**","/resources/**","/v2/api-docs", 
        				"/swagger-resources/configuration/ui","/swagger-resources","/swagger-resources/configuration/security",
        				"/doc.html","/css/**","/js/**","/static/**","/favicon.ico");
    }
	
	@Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login"); //自定义的登陆页面
        registry.addViewController("/oauth/confirm_access").setViewName("oauth_approval"); //自定义的授权页面
    }
	
//	@Override
//    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//		registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//		registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/img/");
//		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/static/");
//		registry.addResourceHandler("/index.html").addResourceLocations("classpath:/static/index.html");
//    }
	
	@Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 1.定义一个converters转换消息的对象
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 2.添加fastjson的配置信息，比如: 是否需要格式化返回的json数据
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
//        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        // WriteNonStringKeyAsString 将不是String类型的key转换成String类型，否则前台无法将Json字符串转换成Json对象
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
        		//全局修改日期格式,如果时间是data、时间戳类型，按照这种格式初始化时间 "yyyy-MM-dd HH:mm:ss"
                SerializerFeature.WriteDateUseDateFormat,
        		SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.WriteNullNumberAsZero,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.DisableCheckSpecialChar,
        		SerializerFeature.WriteNonStringKeyAsString);
        fastJsonConfig.setDateFormat("yyyy-MM-dd hh:mm:ss");
        
        // 中文乱码解决方案
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON); // 设定json格式且编码为UTF-8
        fastConverter.setSupportedMediaTypes(mediaTypes);
        
        // 注册序列化器到容器中，解决FastJson对Oauth2结果序列化格式偏差的问题
        fastJsonConfig.getSerializeConfig().put(DefaultOAuth2AccessToken.class, new Oauth2AccessTokenFastJsonSerializer());

        // 注册序列化器到容器中，解决FastJson对Oauth2异常返回序列化格式偏差的问题
        fastJsonConfig.getSerializeConfig().put(CustomOauthException.class, new CustomOauthExceptionFastJsonSerializer());

        // 3.在converter中添加配置信息
        fastConverter.setFastJsonConfig(fastJsonConfig);
        // 4.返回HttpMessageConverters对象
        return new HttpMessageConverters(fastConverter);
    }
}

/**
 * @Description 针对 Oauth2 开发 FastJson 序列化器
 * @ClassName MvcConfig.java
 * @Package com.kqzz.oauth2.config 
 * @author lipeng
 * @date 2021年12月16日 下午3:24:52
 * 
 * @update_Desc
 * @update_Author
 * @update_Date
 */
class Oauth2AccessTokenFastJsonSerializer implements ObjectSerializer {
	
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, 
    		java.lang.reflect.Type fieldType, int features) throws IOException {
        SerializeWriter out = serializer.out;
        Map<String, Object> token = this.fastJsonSerializerOAuth2Token((OAuth2AccessToken)object);

        String strToken = JSONObject.toJSONString(token);

        out.write(strToken);
    }

    private Map<String, Object> fastJsonSerializerOAuth2Token(OAuth2AccessToken token) {
    	// 创建结果集包装容器
        Map<String, Object> tokenMap = new HashMap<>();
		// access_token 赋值
        tokenMap.put(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
        // token_type 赋值
        tokenMap.put(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
		// 是否存在 refresh_token
        OAuth2RefreshToken refreshToken = token.getRefreshToken();
        if (refreshToken != null) {
            tokenMap.put(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
        }
		// 是否存在超时时间
        Date expiration = token.getExpiration();
        if (expiration != null) {
            long now = System.currentTimeMillis();
            tokenMap.put(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
        }
		// 多个token作用范围需要处理，单不能为空
        Set<String> scope = token.getScope();
        if (scope != null && !scope.isEmpty()) {
            StringBuilder scopes = new StringBuilder();
            for (String s : scope) {
                Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
                scopes.append(s);
                scopes.append(" ");
            }
            tokenMap.put(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
        }
		// 其余参数的处理
        Map<String, Object> additionalInformation = token.getAdditionalInformation();
        for (String key : additionalInformation.keySet()) {
            tokenMap.put(key, additionalInformation.get(key));
        }

        return tokenMap;
    }
}

/**
 * @Description 自定义用户登录异常结果的返回结构 (仅适用FastJson 序列化)
 * @ClassName MvcConfig.java
 * @Package com.kqzz.oauth2.config 
 * @author lipeng
 * @date 2022年01月05日 下午3:24:52
 * 
 * @update_Desc
 * @update_Author
 * @update_Date
 */
class CustomOauthExceptionFastJsonSerializer implements ObjectSerializer {
	
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, 
    		java.lang.reflect.Type fieldType, int features) throws IOException {
        SerializeWriter out = serializer.out;
        Map<String, Object> token = this.fastJsonSerializerOAuth2Token((OAuth2Exception)object);

        String strToken = JSONObject.toJSONString(token);

        out.write(strToken);
    }

    private Map<String, Object> fastJsonSerializerOAuth2Token(OAuth2Exception token) {
    	// 创建结果集包装容器
        Map<String, Object> tokenMap = new HashMap<>();
		
        String msg = token.getMessage();
		int errorNo = 0;
		if(StringUtils.contains(token.getMessage(), "Invalid scope")){
			msg = "scope参数错误";
		}else if(StringUtils.contains(token.getMessage(), "grant type")){
			msg = "grant_type参数错误或缺失";
		}else if(StringUtils.contains(token.getMessage(), "Bad credentials") 
				|| StringUtils.contains(token.getMessage(), "坏的凭证")){
			msg = "登录失败，请核对账户密码";
		}else if(StringUtils.contains(token.getMessage(), "天后将到期")){
			errorNo = 99;
		}
		
		tokenMap.put("errorNo", errorNo);
		tokenMap.put("errorMsg", msg);
        return tokenMap;
    }
}